package logs

import (
	"bytes"
	"context"
	"fmt"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"net/http/httputil"
	"os"
	"path/filepath"
	"runtime/debug"
	"strings"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"gopkg.in/natefinch/lumberjack.v2"
)

var LG *zap.Logger           // 极致性能使用
var Sugar *zap.SugaredLogger // 业务代码使用

const (
	ZapLoggerKey = "zapLogger"
)

var shanghaiLocation *time.Location // 存储加载的时区

type LogConfig struct {
	DebugFileName string `json:"debugFileName"`
	InfoFileName  string `json:"infoFileName"`
	WarnFileName  string `json:"warnFileName"`
	MaxSize       int    `json:"maxSize"`
	MaxAge        int    `json:"maxAge"`
	MaxBackups    int    `json:"maxBackups"`
}

func init() {
	var err error
	shanghaiLocation, err = time.LoadLocation("Asia/Shanghai")
	if err != nil {
		log.Println("无法加载 Asia/Shanghai 时区，将回退到UTC时间，请确保系统安装时区数据库")
		shanghaiLocation = time.UTC
	}
}

// InitLogger 初始化Logger
func InitLogger(cfg *LogConfig) (err error) {
	writeSyncerDebug := getLogWriter(cfg.DebugFileName, cfg.MaxSize, cfg.MaxBackups, cfg.MaxAge)
	writeSyncerInfo := getLogWriter(cfg.InfoFileName, cfg.MaxSize, cfg.MaxBackups, cfg.MaxAge)
	writeSyncerWarn := getLogWriter(cfg.WarnFileName, cfg.MaxSize, cfg.MaxBackups, cfg.MaxAge)
	encoder := getEncoder()
	//文件输出
	debugCore := zapcore.NewCore(encoder, writeSyncerDebug, zapcore.DebugLevel)
	infoCore := zapcore.NewCore(encoder, writeSyncerInfo, zapcore.InfoLevel)
	warnCore := zapcore.NewCore(encoder, writeSyncerWarn, zapcore.WarnLevel)

	// =====控制台配置=====
	// 创建开发环境的编码器配置
	devEncoderConfig := zap.NewDevelopmentEncoderConfig()
	// 修改时间格式化函数
	devEncoderConfig.EncodeTime = customTimeEncoder
	// 配置颜色
	//devEncoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder
	devEncoderConfig.EncodeLevel = customLevelEncoder

	// 自定义宽度的caller编码器
	devEncoderConfig.EncodeCaller = customCallerEncoder

	// 使用修改过的配置创建控制台编码器
	std := zapcore.NewCore(
		zapcore.NewConsoleEncoder(devEncoderConfig),
		zapcore.Lock(os.Stdout),
		zapcore.DebugLevel,
	)

	//标准输出
	core := zapcore.NewTee(debugCore, infoCore, warnCore, std)
	LG = zap.New(core, zap.AddCaller())
	Sugar = LG.Sugar()
	zap.ReplaceGlobals(LG) // 替换zap包中全局的logger实例，后续在其他包中只需使用zap.L()调用即可
	return
}

func getLogWriter(filename string, maxSize, maxBackup, maxAge int) zapcore.WriteSyncer {

	// 1. 获取文件所在的目录
	dir := filepath.Dir(filename)

	// 2. 使用 os.MkdirAll 递归创建目录树，如果目录已存在则什么都不做 0755 是一个通用的目录权限设置
	if err := os.MkdirAll(dir, 0755); err != nil {
		log.Fatalf("无法创建日志目录 %s: %v", dir, err)
	}

	lumberJackLogger := &lumberjack.Logger{
		Filename:   filename,
		MaxSize:    maxSize,
		MaxBackups: maxBackup,
		MaxAge:     maxAge,
	}

	return zapcore.AddSync(lumberJackLogger)
}

func getEncoder() zapcore.Encoder {
	encoderConfig := zap.NewProductionEncoderConfig()
	//encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
	encoderConfig.EncodeTime = customTimeEncoder
	encoderConfig.TimeKey = "time"
	//encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
	encoderConfig.EncodeLevel = customLevelEncoder
	encoderConfig.EncodeDuration = zapcore.SecondsDurationEncoder
	encoderConfig.EncodeCaller = zapcore.ShortCallerEncoder //文件路径:行号
	return zapcore.NewJSONEncoder(encoderConfig)
}

func customTimeEncoder(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
	enc.AppendString(t.In(shanghaiLocation).Format("2006/01/02 15:04:05.000"))
}

func customCallerEncoder(caller zapcore.EntryCaller, enc zapcore.PrimitiveArrayEncoder) {

	shortPath := caller.TrimmedPath()
	//callerStr := fmt.Sprintf("%s:%d", shortPath, caller.Line)
	callerStr := fmt.Sprintf("%s", shortPath)

	const fixedWidth = 25 //定义合适的宽度

	formattedCaller := fmt.Sprintf("%-*s", fixedWidth, callerStr) // 格式化，左对齐并补足空格

	enc.AppendString(formattedCaller) //添加到最终的日志中

}

func customLevelEncoder(level zapcore.Level, enc zapcore.PrimitiveArrayEncoder) {
	var levelStr string
	var color string

	switch level {
	case zapcore.DebugLevel:
		levelStr = "[DEB]"
		color = "\033[36m" // 青色
	case zapcore.InfoLevel:
		levelStr = "[INF]"
		color = "\033[32m" // 绿色
	case zapcore.WarnLevel:
		levelStr = "[WRN]"
		color = "\033[33m" // 黄色
	case zapcore.ErrorLevel:
		levelStr = "[ERR]"
		color = "\033[31m" // 红色
	case zapcore.DPanicLevel:
		levelStr = "[DPN]"
		color = "\033[35m" // 洋红色
	case zapcore.PanicLevel:
		levelStr = "[PNC]"
		color = "\033[35m" // 洋红色
	case zapcore.FatalLevel:
		levelStr = "[FTL]"
		color = "\033[35m" // 洋红色
	default:
		levelStr = "[UNK]"
		color = "\033[37m" // 白色
	}

	// 添加颜色并重置
	coloredLevel := fmt.Sprintf("%s%s\033[0m", color, levelStr)
	enc.AppendString(coloredLevel)
}

type responseBodyWriter struct {
	gin.ResponseWriter
	body *bytes.Buffer
}

// 响应体被写入时，这个方法会被调用
func (w responseBodyWriter) Write(b []byte) (int, error) {
	w.body.Write(b)
	return w.ResponseWriter.Write(b)

}

// GinLogger 接收gin框架默认的日志（带 Request-ID）
func GinLogger() gin.HandlerFunc {
	return func(c *gin.Context) {
		requestID := uuid.New().String()[:8]

		ctxWithTraceID := context.WithValue(c.Request.Context(), traceIDKey{}, requestID)

		c.Request = c.Request.WithContext(ctxWithTraceID)

		path := c.Request.URL.Path
		requestLogFields := []zap.Field{
			zap.String("requestID", requestID),
			zap.String("method", c.Request.Method),
			zap.String("path", path),
			zap.String("ip", c.ClientIP()),
		}

		contentType := c.GetHeader("Content-Type")

		if strings.Contains(contentType, "multipart/form-data") {
			// 对于文件，只记录表单字段，不记录文件内容 32 << 20 表示最大内存占用为32MB
			if err := c.Request.ParseMultipartForm(32 << 20); err == nil {
				requestLogFields = append(requestLogFields, zap.Any("form-data", c.Request.MultipartForm.Value))
			}
		} else {
			if c.Request.Body != nil {
				requestBody, _ := ioutil.ReadAll(c.Request.Body)
				c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(requestBody))
				if len(requestBody) > 0 {
					logBody := string(requestBody)
					if len(logBody) > 1024 { // 设置最大长度
						logBody = logBody[:1024] + "..." // 截断并添加省略号
					}
					requestLogFields = append(requestLogFields, zap.String("req_body", logBody))
				}
			}
		}

		LG.Info("[request] ", requestLogFields...)

		// 记录响应体：替换 writer
		blw := &responseBodyWriter{
			ResponseWriter: c.Writer,
			body:           bytes.NewBufferString(""),
		}
		c.Writer = blw

		c.Next()

		// 响应日志
		//cost := time.Since(start)

		responseFields := []zap.Field{
			zap.String("requestID", requestID),
			zap.Int("status", c.Writer.Status()),
		}

		// 添加响应体
		if blw.body.Len() > 0 {
			respBody := blw.body.String()
			if len(respBody) > 1024 { // 设置最大长度
				respBody = respBody[:1024] + "..." // 截断并添加省略号
			}
			responseFields = append(responseFields, zap.String("resp_body", respBody))
		}

		// 打印“响应请求”日志
		if c.Writer.Status() >= 400 {
			LG.Error("[response]", responseFields...)
		} else {
			LG.Info("[response]", responseFields...)
		}
	}
}

// GinRecovery recover掉项目可能出现的panic，并使用zap记录相关日志
func GinRecovery(stack bool) gin.HandlerFunc {
	return func(c *gin.Context) {
		// 获取 logger
		logger, _ := c.Get(ZapLoggerKey)
		log, ok := logger.(*zap.Logger)
		if !ok {
			// 如果获取失败，则使用全局 logger
			log = LG
		}
		defer func() {
			if err := recover(); err != nil {
				// Check for a broken connection, as it is not really a
				// condition that warrants a panic stack trace.
				var brokenPipe bool
				if ne, ok := err.(*net.OpError); ok {
					if se, ok := ne.Err.(*os.SyscallError); ok {
						if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") {
							brokenPipe = true
						}
					}
				}

				httpRequest, _ := httputil.DumpRequest(c.Request, false)
				if brokenPipe {
					LG.Error(c.Request.URL.Path,
						zap.Any("error", err),
						zap.String("request", string(httpRequest)),
					)
					// If the connection is dead, we can't write a status to it.
					c.Error(err.(error)) // nolint: errcheck
					c.Abort()
					return
				}

				if stack {
					log.Error("[Recovery from panic]",
						zap.Any("error", err),
						zap.String("request", string(httpRequest)),
						zap.String("stack", string(debug.Stack())),
					)
				} else {
					log.Error("[Recovery from panic]",
						zap.Any("error", err),
						zap.String("request", string(httpRequest)),
					)
				}
				c.AbortWithStatus(http.StatusInternalServerError)
			}
		}()
		c.Next()
	}
}

// 定义一个私有的key类型
type traceIDKey struct {
}

// SugarWithContext 返回一个带有 traceID 的 *zap.SugaredLogger (新函数！)
func SugarWithContext(ctx context.Context) *zap.SugaredLogger {
	// 内部直接调用 WithContext，然后转换成 Sugar
	return WithContext(ctx).Sugar()
}

func WithContext(ctx context.Context) *zap.Logger {

	traceID := GetTraceIDFromContext(ctx)

	if traceID != "" {
		return LG.With(zap.String("traceID", traceID))
	}

	return LG
}

func GetTraceIDFromContext(ctx context.Context) string {
	if id, ok := ctx.Value(traceIDKey{}).(string); ok {
		return id
	}
	return ""
}
